/******************************************************************************/
/* Mednafen - Multi-system Emulator                                           */
/******************************************************************************/
/* tblur.cpp:
**  Copyright (C) 2007-2016 Mednafen Team
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License
** as published by the Free Software Foundation; either version 2
** of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation, Inc.,
** 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include <mednafen/mednafen.h>
#include "tblur.h"

struct HQPixelEntry
{
 uint16 a, b, c, d;
};

static std::unique_ptr<uint32[]> BlurBuf;
static uint32 AccumBlurAmount; // max of 16384, infinite blur!
static std::unique_ptr<HQPixelEntry[]> AccumBlurBuf;

void TBlur_Init(void)
{
 try
 {
        std::string sn = MDFNGameInfo->shortname;

        if(MDFN_GetSettingB(std::string(sn + "." + std::string("tblur"))))
        {
         AccumBlurAmount = (uint32)(16384 * MDFN_GetSettingF(std::string(sn + "." + std::string("tblur.accum.amount"))) / 100);
         if(MDFN_GetSettingB(std::string(sn + "." + std::string("tblur.accum"))))
         {
          AccumBlurBuf.reset(new HQPixelEntry[MDFNGameInfo->fb_width * MDFNGameInfo->fb_height]);
          MDFN_printf(_("Video temporal frame blur enabled with accumulation: %f.\n"), (double)AccumBlurAmount * 100 / 16384);
         }
         else
         {
          BlurBuf.reset(new uint32[MDFNGameInfo->fb_width * MDFNGameInfo->fb_height]);
          MDFN_printf(_("Video temporal frame blur enabled.\n"));
         }
        }
 }
 catch(...)
 {
  BlurBuf.reset(nullptr);
  AccumBlurBuf.reset(nullptr);
  throw;
 }
}

void TBlur_Run(EmulateSpecStruct *espec)
{
 MDFN_Surface *surface = espec->surface;
 uint32 *pXBuf = surface->pixels;
 const uint32 bb_pitch = MDFNGameInfo->fb_width;

 if(surface->format.bpp != 32)
 {
  puts("Temporal blur not implemented for non-32bpp formats yet."); // Mostly for debugging purposes.
  return;
 }

 //printf("%d %d %d %d\n", espec->surface->format.Rshift, espec->surface->format.Gshift, espec->surface->format.Bshift, espec->surface->format.Ashift);
 if(AccumBlurBuf)
 {
  for(int y = 0; y < espec->DisplayRect.h; y++)
  {
   int xw = espec->DisplayRect.w;
   int xs = espec->DisplayRect.x;

   if(espec->LineWidths[0] != ~0)
   {
    xw = espec->LineWidths[espec->DisplayRect.y + y];
   }

   if(AccumBlurAmount == 8192)
   {
    for(int x = 0; x < xw; x++)
    {
     uint32 color;
     HQPixelEntry mixcolor;

     color = pXBuf[(y + espec->DisplayRect.y) * surface->pitch32 + xs + x];
     mixcolor = AccumBlurBuf[y * bb_pitch + x];
     mixcolor.a = ((uint32)mixcolor.a + ((color & 0xFF) << 8)) >> 1;
     mixcolor.b = ((uint32)mixcolor.b + ((color & 0xFF00))) >> 1;
     mixcolor.c = ((uint32)mixcolor.c + ((color & 0xFF0000) >> 8)) >> 1;
     mixcolor.d = ((uint32)mixcolor.d + ((color & 0xFF000000) >> 16)) >> 1;

     AccumBlurBuf[y * bb_pitch + x] = mixcolor;

     pXBuf[(y + espec->DisplayRect.y) * surface->pitch32 + xs + x] = ((mixcolor.a >> 8) << 0) | ((mixcolor.b >> 8) << 8) |
        ((mixcolor.c >> 8) << 16) | ((mixcolor.d >> 8) << 24);
    }
   }
   else
   {
    uint32 InvAccumBlurAmount = 16384 - AccumBlurAmount;

    for(int x = 0; x < xw; x++)
    {
     uint32 color;
     HQPixelEntry mixcolor;
     color = pXBuf[(y + espec->DisplayRect.y) * surface->pitch32 + xs + x];

     mixcolor = AccumBlurBuf[y * bb_pitch + x];
     mixcolor.a = ((uint32)mixcolor.a * AccumBlurAmount + InvAccumBlurAmount * ((color & 0xFF) << 8)) >> 14;
     mixcolor.b = ((uint32)mixcolor.b * AccumBlurAmount + InvAccumBlurAmount * ((color & 0xFF00))) >> 14;
     mixcolor.c = ((uint32)mixcolor.c * AccumBlurAmount + InvAccumBlurAmount * ((color & 0xFF0000) >> 8)) >> 14;
     mixcolor.d = ((uint32)mixcolor.d * AccumBlurAmount + InvAccumBlurAmount * ((color & 0xFF000000) >> 16)) >> 14;
     AccumBlurBuf[y * bb_pitch + x] = mixcolor;

     pXBuf[(y + espec->DisplayRect.y) * surface->pitch32 + xs + x] = ((mixcolor.a >> 8) << 0) | ((mixcolor.b >> 8) << 8) |
        ((mixcolor.c >> 8) << 16) | ((mixcolor.d >> 8) << 24);
    }
   }

  }
 }
 else if(BlurBuf)
 {
  for(int y = 0; y < espec->DisplayRect.h; y++)
  {
   int xw = espec->DisplayRect.w;
   int xs = espec->DisplayRect.x;

   if(espec->LineWidths[0] != ~0)
   {
    xw = espec->LineWidths[espec->DisplayRect.y + y];
   }

   for(int x = 0; x < xw; x++)
   {
    uint32 color, mixcolor;
    color = pXBuf[(y + espec->DisplayRect.y) * surface->pitch32 + xs + x];

    mixcolor = BlurBuf[y * bb_pitch + x];
    BlurBuf[y * bb_pitch + x] = color;

    // Needs 64-bit
    #ifdef HAVE_NATIVE64BIT
    color = ((((uint64)color + mixcolor) - ((color ^ mixcolor) & 0x01010101))) >> 1;
    #else
    color = ((((color & 0x00FF00FF) + (mixcolor & 0x00FF00FF)) >> 1) & 0x00FF00FF) | (((((color & 0xFF00FF00) >> 1) + ((mixcolor & 0xFF00FF00) >> 1))) & 0xFF00FF00);
    #endif

    //    color = (((color & 0xFF) + (mixcolor & 0xFF)) >> 1) | ((((color & 0xFF00) + (mixcolor & 0xFF00)) >> 1) & 0xFF00) |
    //       ((((color & 0xFF0000) + (mixcolor & 0xFF0000)) >> 1) & 0xFF0000) | ((((color >> 24) + (mixcolor >> 24)) >> 1) << 24);
    pXBuf[(y + espec->DisplayRect.y) * surface->pitch32 + xs + x] = color;
   }
  }
 }
}

void TBlur_Kill(void)
{
 BlurBuf.reset(nullptr);
 AccumBlurBuf.reset(nullptr);
}

bool TBlur_IsOn(void)
{
 return(BlurBuf || AccumBlurBuf);
}
